/*
 * Util
 * Version: 0.1
 * Date: 2008-10-25
 * Author: gp08-nhn
 */

package keyboardhero;

import java.awt.*;
import java.awt.RenderingHints.Key;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.geom.AffineTransform;
import java.awt.image.*;
import java.awt.image.renderable.RenderableImage;
import java.io.*;
import java.text.*;
import java.net.*;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.swing.*;
import javax.swing.text.*;
import javax.swing.border.*;
import javax.swing.event.*;
import keyboardhero.DialogSongList.*;

/**
 * This class provides utility functions to all the other methods. This class handles the data
 * transactions from outside the application including the file handling on the hard disk and the
 * internet connections to a server, which provides capability for the updating and online toplist
 * features.
 * <p>
 * This class reads the properties and language files in and saves the properties when the game
 * exits. Also this class provides the debugging methods for the other classes.
 * 
 * <p align="center">
 * <br>
 * <a name="class_diagram" id="class_diagram"><br>
 * </a> <img src="{@docRoot}/resources/ClassDiagrams/Util.png" class="dontPrintIt" alt="Class
 * Diagram for Util" title="Class Diagram for Util" align="center"> <img src="{@docRoot}/resources/ClassDiagrams/Util_print.png"
 * class="onlyPrintIt" alt="Class Diagram for Util" title="Class Diagram for Util" align="center">
 * </p>
 * 
 * @author {@value keyboardhero.KeyboardHero#APP_AUTHORS}
 * @version 0.9 2008-04-19
 */
final class Util {
	
	//-------------------- NESTED CLASSES --------------------
	
	// ××××××××××××××× Obj CLASS ×××××××××××××××
	
	/**
	 * This class is used for reference emulating with an optional modification event, which is
	 * triggered if the object hold by this reference is modified. It can also have an id to check
	 * whether some modification has been done on this reference.
	 * 
	 * @param <T>
	 *            the type of the object hold by this reference.
	 */
	final static class Obj<T> {
		private T obj;
		private Runnable onModification = null;
		private volatile int id = 0;
		
		Obj(T obj) {
			this.obj = obj;
		}
		
		Obj(T obj, Runnable onModification) {
			this.obj = obj;
			this.onModification = onModification;
		}
		
		final T getObj() {
			return obj;
		}
		
		final void setObj(T obj) {
			if(!Util.equals(this.obj, obj)) {
				this.obj = obj;
				if(onModification != null) {
					onModification.run();
				}
			}
		}
		
		final Runnable getOnModification() {
			return onModification;
		}
		
		final void setOnModification(Runnable onModification) {
			this.onModification = onModification;
		}
		
		final int getId() {
			return id;
		}
		
		final int incrementId() {
			return ++id;
		}
	}
	
	// ××××××××××××××× NamedTitledBorder CLASS ×××××××××××××××
	
	final static class NamedTitledBorder extends TitledBorder {
		/**
		 * A TitledBorder is a serializable object, thus it requires a unique Serial Version ID.
		 * 
		 * @see java.io.Serializable
		 */
		private final static long serialVersionUID = 9048454654256635671L;
		
		private String name;
		
		public NamedTitledBorder(String name) {
			super(Util.getMsg(name));
			this.name = name;
		}
		
		final String getName() {
			return name;
		}
		
		final void setName(String name) {
			this.name = name;
		}
	}
	
	// ××××××××××××××× NumericDocument CLASS ×××××××××××××××
	
	final static class NumericDocument extends PlainDocument {
		private final static long serialVersionUID = -7227522263433215131L;
		
		public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
			if(str == null) return;
			char[] txt = str.toCharArray();
			for(int i = 0; i < txt.length; ++i) {
				if(Character.isDigit(txt[i]) && super.getLength() < 5) {
					super.insertString(offs++, Character.toString(txt[i]), a);
				}
			}
		}
	};
	
	// ××××××××××××××× Item CLASS ×××××××××××××××
	
	final static class Item<K, V> {
		private K key;
		private V value;
		private String name = null;
		
		Item(K key, V value) {
			this.key = key;
			this.value = value;
		}
		
		Item(K key, V value, String name) {
			this(key, value);
			this.name = name;
		}
		
		final public String toString() {
			return value.toString();
		}
		
		final K getKey() {
			return key;
		}
		
		final void setKey(K key) {
			this.key = key;
		}
		
		final V getValue() {
			return value;
		}
		
		final void setValue(V value) {
			this.value = value;
		}
		
		final String getName() {
			return name;
		}
		
		final void setName(String name) {
			this.name = name;
		}
		
		/*
		// not needed
		public boolean equals(Object obj) {
			if(obj instanceof Item) {
				if(obj == null) return false;
				Item<?,?> o = (Item<?,?>)obj;
				if(name == null) {
					if(o.name == null) {
						if(value == null) return o.value == null;
						return value.equals(o.value);
					} else {
						return false;
					}
				} else {
					return name.equals(o.name);
				}
			} else if(obj instanceof String) {
				String o = (String)obj;
				if(name == null) {
					if(value == null) return obj == null;
					return value.equals(o);
				} else {
					return Util.getMsg(name, name).equals(obj);
				}
			}
			return false;
		} //*/
	}
	
	// ××××××××××××××× DynamicGridLayout CLASS ×××××××××××××××
	
	final static class DynamicGridLayout extends GridLayout {
		
		//********** VARIABLES **********
		
		private final static long serialVersionUID = -7777155286674565087L; //$SUP-DIH$ //$SUP-FUTV$
		
		//********** CORE FUNCTIONS **********
		
		DynamicGridLayout() {
			super();
		}
		
		DynamicGridLayout(int rows, int cols) {
			super(rows, cols);
		}
		
		DynamicGridLayout(int rows, int cols, int hgap, int vgap) {
			super(rows, cols, hgap, vgap);
		}
		
		//********** OVERRIDING FUNCTIONS **********
		
		public Dimension preferredLayoutSize(Container parent) {
			synchronized (parent.getTreeLock()) {
				final int componentsnum = parent.getComponentCount();
				int rowsnum = getRows();
				int colsnum = getColumns();
				
				if(rowsnum > 0) {
					colsnum = (componentsnum + rowsnum - 1) / rowsnum;
				} // end if
				else {
					rowsnum = (componentsnum + colsnum - 1) / colsnum;
				} // end else
				
				int[] widths = new int[colsnum];
				int[] heights = new int[rowsnum];
				Dimension dimension;
				for(int i = 0; i < componentsnum; i++) {
					int row = i / colsnum;
					int col = i % colsnum;
					
					dimension = parent.getComponent(i).getPreferredSize();
					if(widths[col] < dimension.width) {
						widths[col] = dimension.width;
					} // end if
					if(heights[row] < dimension.height) {
						heights[row] = dimension.height;
					} // end if
				} // end for
				
				int widthsum = 0;
				for(int j = 0; j < colsnum; j++) {
					widthsum += widths[j];
				} // end for
				
				int heightsum = 0;
				for(int i = 0; i < rowsnum; i++) {
					heightsum += heights[i];
				} // end for
				
				Insets insets = parent.getInsets();
				return new Dimension(insets.left + insets.right + widthsum + (colsnum - 1) * getHgap(), insets.top + insets.bottom + heightsum + (rowsnum - 1) * getVgap());
			} // end synchronized
		} // end method
		
		public Dimension minimumLayoutSize(Container parent) {
			synchronized (parent.getTreeLock()) {
				final int componentsnum = parent.getComponentCount();
				int rowsnum = getRows();
				int colsnum = getColumns();
				
				if(rowsnum > 0) {
					colsnum = (componentsnum + rowsnum - 1) / rowsnum;
				} // end if
				else {
					rowsnum = (componentsnum + colsnum - 1) / colsnum;
				} // end else
				
				int[] widths = new int[colsnum];
				int[] heights = new int[rowsnum];
				Dimension dimension;
				for(int i = 0; i < componentsnum; i++) {
					int row = i / colsnum;
					int col = i % colsnum;
					
					dimension = parent.getComponent(i).getMinimumSize();
					if(widths[col] < dimension.width) {
						widths[col] = dimension.width;
					} // end if
					if(heights[row] < dimension.height) {
						heights[row] = dimension.height;
					} // end if
				} // end for
				
				int widthsum = 0;
				for(int j = 0; j < colsnum; j++) {
					widthsum += widths[j];
				} // end for
				
				int heightsum = 0;
				for(int i = 0; i < rowsnum; i++) {
					heightsum += heights[i];
				} // end for
				
				Insets insets = parent.getInsets();
				return new Dimension(insets.left + insets.right + widthsum + (colsnum - 1) * getHgap(), insets.top + insets.bottom + heightsum + (rowsnum - 1) * getVgap());
			} // end synchronized
		} // end method
		
		public void layoutContainer(Container parent) {
			synchronized (parent.getTreeLock()) {
				final int componentsnum = parent.getComponentCount();
				if(componentsnum == 0) {
					return;
				} // end if
				
				int rowsnum = getRows();
				int colsnum = getColumns();
				if(rowsnum > 0) {
					colsnum = (componentsnum + rowsnum - 1) / rowsnum;
				} // end if
				else {
					rowsnum = (componentsnum + colsnum - 1) / colsnum;
				} // end else
				
				int hgap = getHgap();
				int vgap = getVgap();
				
				Dimension dimension = preferredLayoutSize(parent);
				double widthratio = (1.0 * parent.getWidth()) / dimension.width;
				double heightratio = (1.0 * parent.getHeight()) / dimension.height;
				
				int[] widths = new int[colsnum];
				int[] heights = new int[rowsnum];
				for(int i = 0; i < componentsnum; i++) {
					int row = i / colsnum;
					int col = i % colsnum;
					
					dimension = parent.getComponent(i).getPreferredSize();
					dimension.width *= widthratio;
					dimension.height *= heightratio;
					if(widths[col] < dimension.width) {
						widths[col] = dimension.width;
					} // end if
					if(heights[row] < dimension.height) {
						heights[row] = dimension.height;
					} // end if
				} // end for
				
				Insets insets = parent.getInsets();
				for(int col = 0, x = insets.left; col < colsnum; col++) {
					for(int row = 0, y = insets.top; row < rowsnum; row++) {
						int i = row * colsnum + col;
						if(i < componentsnum) {
							parent.getComponent(i).setBounds(x, y, widths[col], heights[row]);
						} // end if
						y += heights[row] + vgap;
					} // end for
					x += widths[col] + hgap;
				} // end for
			} // end synchronized
		} // end method
	} // end class
	
	// ××××××××××××××× Status CLASS ×××××××××××××××
	
	final static class Status implements Comparable<Status> {
		/** Used for error messages. */
		static final int ERROR = 0;
		/** Used for information messages. */
		static final int INFORMATION = 1;
		/** Used for warning messages. */
		static final int WARNING = 2;
		/** Used for questions. */
		static final int QUESTION = 3;
		/** No icon is used. */
		static final int PLAIN = -1;
		
		static final String[] ICON_TEXTS = new String[] {"OptionPane.errorIcon", "OptionPane.informationIcon", "OptionPane.warningIcon", "OptionPane.questionIcon"};
		
		final int priority, icon;
		final String msgKey;
		private String exception;
		
		Status(String msgKey) {
			this(msgKey, null, PLAIN, 0);
		}
		
		Status(String msgKey, int icon) {
			this(msgKey, null, icon, 0);
		}
		
		Status(String msgKey, int icon, int priority) {
			this(msgKey, null, icon, priority);
		}
		
		Status(String msgKey, String exception) {
			this(msgKey, exception, PLAIN, 0);
		}
		
		Status(String msgKey, String exception, int icon) {
			this(msgKey, exception, icon, 0);
		}
		
		Status(String msgKey, String exception, int icon, int priority) {
			this.msgKey = msgKey;
			this.exception = exception;
			this.icon = icon;
			this.priority = priority;
		}
		
		final String getException() {
			return exception;
		}
		
		final void setException(String exception) {
			this.exception = exception;
		}
		
		public boolean equals(Object o) {
			if(o == null) return false;
			if(o instanceof Status) {
				Status s = (Status)o;
				if(msgKey == null) {
					return s.msgKey == null && icon == s.icon && priority == s.priority;
				} else {
					return msgKey.equals(s.msgKey) && icon == s.icon && priority == s.priority;
				}
			} else {
				return false;
			}
		}
		
		public int compareTo(Status o) {
			if(priority < o.priority) {
				return 1;
			} else if(priority == o.priority) {
				if(icon < o.icon) {
					return -1;
				} else if(icon == o.icon) {
					if(msgKey == null) {
						return o.msgKey == null ? 0 : -1;
					} else {
						return msgKey.compareTo(o.msgKey);
					}
				} else {
					return 1;
				}
			} else {
				return -1;
			}
		}
	}
	
	// ××××××××××××××× ScaledIcon CLASS ×××××××××××××××
	
	final static class ScaledIcon implements Icon {
		private Icon source;
		private float ratiox, ratioy;
		private int hints;
		
		ScaledIcon(Icon source, int width, int height, int hints) {
			this.source = source;
			this.hints = hints;
			ratiox = (float)width / source.getIconWidth();
			ratioy = (float)height / source.getIconHeight();
		}
		
		private final int scalex(int x) {
			return (int)(ratiox * x);
		}
		
		private final int scaley(int y) {
			return (int)(ratioy * y);
		}
		
		public final int getIconHeight() {
			return scaley(source.getIconHeight());
		}
		
		public final int getIconWidth() {
			return scalex(source.getIconWidth());
		}
		
		public void paintIcon(Component c, Graphics g, int x, int y) {
			source.paintIcon(c, new ScaledGraphics((Graphics2D)g, ratiox, ratioy, hints), x, y);
		}
	}
	
	// ××××××××××××××× ScaledGraphics CLASS ×××××××××××××××
	
	final static class ScaledGraphics extends Graphics2D {
		private Graphics2D source;
		private float ratiox, ratioy;
		private int hints;
		
		ScaledGraphics(Graphics2D source, float ratiox, float ratioy, int hints) {
			this.source = source;
			this.ratiox = ratiox;
			this.ratioy = ratioy;
			this.hints = hints;
		}
		
		private final int scalex(int x) {
			return (int)(ratiox * x);
		}
		
		private final int scaley(int y) {
			return (int)(ratioy * y);
		}
		
		private final float scalex(float x) {
			return (ratiox * x);
		}
		
		private final float scaley(float y) {
			return (ratioy * y);
		}
		
		private final double scalex(double x) {
			return (ratiox * x);
		}
		
		private final double scaley(double y) {
			return (ratioy * y);
		}
		
		@Override
		public void addRenderingHints(Map<?, ?> hints) {
			source.addRenderingHints(hints);
		}
		
		@Override
		public void clip(Shape s) {
			source.clip(s);
		}
		
		@Override
		public void draw(Shape s) {
			source.draw(s);
		}
		
		@Override
		public void drawGlyphVector(GlyphVector g, float x, float y) {
			source.drawGlyphVector(g, x, y);
		}
		
		@Override
		public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) {
			return source.drawImage(img.getScaledInstance(scalex(img.getWidth(obs)), scaley(img.getHeight(obs)), hints), xform, obs);
		}
		
		@Override
		public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) {
			source.drawImage(img.getScaledInstance(scalex(img.getWidth(null)), scaley(img.getHeight(null)), hints), scalex(x), scaley(y), null);
		}
		
		@Override
		public void drawRenderableImage(RenderableImage img, AffineTransform xform) {
			throw new UnsupportedOperationException();
		}
		
		@Override
		public void drawRenderedImage(RenderedImage img, AffineTransform xform) {
			throw new UnsupportedOperationException();
		}
		
		@Override
		public void drawString(String str, int x, int y) {
			source.drawString(str, scalex(x), scaley(y));
		}
		
		@Override
		public void drawString(String str, float x, float y) {
			source.drawString(str, scalex(x), scaley(y));
		}
		
		@Override
		public void drawString(AttributedCharacterIterator iterator, int x, int y) {
			source.drawString(iterator, scalex(x), scaley(y));
		}
		
		@Override
		public void drawString(AttributedCharacterIterator iterator, float x, float y) {
			source.drawString(iterator, scalex(x), scaley(y));
		}
		
		@Override
		public void fill(Shape s) {
			source.fill(s);
		}
		
		@Override
		public Color getBackground() {
			return source.getBackground();
		}
		
		@Override
		public Composite getComposite() {
			return source.getComposite();
		}
		
		@Override
		public GraphicsConfiguration getDeviceConfiguration() {
			return source.getDeviceConfiguration();
		}
		
		@Override
		public FontRenderContext getFontRenderContext() {
			return source.getFontRenderContext();
		}
		
		@Override
		public Paint getPaint() {
			return source.getPaint();
		}
		
		@Override
		public Object getRenderingHint(Key hintKey) {
			return source.getRenderingHint(hintKey);
		}
		
		@Override
		public RenderingHints getRenderingHints() {
			return source.getRenderingHints();
		}
		
		@Override
		public Stroke getStroke() {
			return source.getStroke();
		}
		
		@Override
		public AffineTransform getTransform() {
			return source.getTransform();
		}
		
		@Override
		public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
			return source.hit(rect, s, onStroke);
		}
		
		@Override
		public void rotate(double theta) {
			source.rotate(theta);
		}
		
		@Override
		public void rotate(double theta, double x, double y) {
			source.rotate(theta, scalex(x), scaley(y));
		}
		
		@Override
		public void scale(double sx, double sy) {
			source.scale(sx, sy);
		}
		
		@Override
		public void setBackground(Color color) {
			source.setBackground(color);
		}
		
		@Override
		public void setComposite(Composite comp) {
			source.setComposite(comp);
		}
		
		@Override
		public void setPaint(Paint paint) {
			source.setPaint(paint);
		}
		
		@Override
		public void setRenderingHint(Key hintKey, Object hintValue) {
			source.setRenderingHint(hintKey, hintValue);
		}
		
		@Override
		public void setRenderingHints(Map<?, ?> hints) {
			source.setRenderingHints(hints);
		}
		
		@Override
		public void setStroke(Stroke s) {
			source.setStroke(s);
		}
		
		@Override
		public void setTransform(AffineTransform xform) {
			source.setTransform(xform);
		}
		
		@Override
		public void shear(double shx, double shy) {
			source.shear(shx, shy);
		}
		
		@Override
		public void transform(AffineTransform xform) {
			source.transform(xform);
		}
		
		@Override
		public void translate(int x, int y) {
			source.translate(scalex(x), scaley(y));
		}
		
		@Override
		public void translate(double tx, double ty) {
			source.translate(scalex(tx), scaley(ty));
		}
		
		@Override
		public void clearRect(int x, int y, int width, int height) {
			source.clearRect(scalex(x), scaley(y), scalex(width), scaley(height));
		}
		
		@Override
		public void clipRect(int x, int y, int width, int height) {
			source.clipRect(scalex(x), scaley(y), scalex(width), scaley(height));
		}
		
		@Override
		public void copyArea(int x, int y, int width, int height, int dx, int dy) {
			source.copyArea(scalex(x), scaley(y), scalex(width), scaley(height), scalex(dx), scaley(dy));
		}
		
		@Override
		public Graphics create() {
			return source.create();
		}
		
		@Override
		public void dispose() {
			source.dispose();
		}
		
		@Override
		public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
			drawArc(scalex(x), scaley(y), scalex(width), scaley(height), startAngle, arcAngle);
		}
		
		@Override
		public boolean drawImage(Image img, int x, int y, ImageObserver observer) {
			return source.drawImage(img, scalex(x), scaley(y), scalex(img.getWidth(observer)), scaley(img.getHeight(observer)), observer);
			//			return source.drawImage(img.getScaledInstance(scalex(img.getWidth(observer)), scaley(img.getHeight(observer)), hints), scalex(x), scaley(y), observer); // works for Nimbus only...
		}
		
		@Override
		public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) {
			return source.drawImage(img, scalex(x), scaley(y), scalex(img.getWidth(observer)), scaley(img.getHeight(observer)), bgcolor, observer);
			//			return source.drawImage(img.getScaledInstance(scalex(img.getWidth(observer)), scaley(img.getHeight(observer)), hints), scalex(x), scaley(y), bgcolor, observer); // works for Nimbus only...
		}
		
		@Override
		public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) {
			return source.drawImage(img.getScaledInstance(scalex(img.getWidth(observer)), scaley(img.getHeight(observer)), hints), scalex(x), scaley(y), scalex(width), scaley(height), observer);
		}
		
		@Override
		public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) {
			return source.drawImage(img.getScaledInstance(scalex(img.getWidth(observer)), scaley(img.getHeight(observer)), hints), scalex(x), scaley(y), scalex(width), scaley(height), bgcolor, observer);
		}
		
		@Override
		public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) {
			return source.drawImage(img.getScaledInstance(scalex(img.getWidth(observer)), scaley(img.getHeight(observer)), hints), scalex(dx1), scaley(dy1), scalex(dx2), scaley(dy2), scalex(sx1), scaley(sy1), scalex(sx2), scaley(sy2), observer);
		}
		
		@Override
		public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer) {
			return source.drawImage(img.getScaledInstance(scalex(img.getWidth(observer)), scaley(img.getHeight(observer)), hints), scalex(dx1), scaley(dy1), scalex(dx2), scaley(dy2), scalex(sx1), scaley(sy1), scalex(sx2), scaley(sy2), bgcolor, observer);
		}
		
		@Override
		public void drawLine(int x1, int y1, int x2, int y2) {
			source.drawLine(scalex(x1), scaley(y1), scalex(x1), scaley(y1));
		}
		
		@Override
		public void drawOval(int x, int y, int width, int height) {
			source.drawOval(scalex(x), scaley(y), scalex(width), scaley(height));
		}
		
		@Override
		public void drawPolygon(int[] points, int[] points2, int points3) {
			points = Arrays.copyOf(points, points.length);
			points2 = Arrays.copyOf(points2, points.length);
			for(int i = 0; i < points.length; ++i)
				points[i] = scalex(points[i]);
			for(int i = 0; i < points2.length; ++i)
				points2[i] = scaley(points2[i]);
			source.drawPolygon(points, points2, points3);
		}
		
		@Override
		public void drawPolyline(int[] points, int[] points2, int points3) {
			points = Arrays.copyOf(points, points.length);
			points2 = Arrays.copyOf(points2, points.length);
			for(int i = 0; i < points.length; ++i)
				points[i] = scalex(points[i]);
			for(int i = 0; i < points2.length; ++i)
				points2[i] = scaley(points2[i]);
			source.drawPolyline(points, points2, points3);
		}
		
		@Override
		public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {
			source.drawRoundRect(scalex(x), scaley(y), scalex(width), scaley(height), scalex(arcWidth), scaley(arcHeight));
		}
		
		@Override
		public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
			source.fillArc(scalex(x), scaley(y), scalex(width), scaley(height), startAngle, arcAngle);
		}
		
		@Override
		public void fillOval(int x, int y, int width, int height) {
			source.fillOval(scalex(x), scaley(y), scalex(width), scaley(height));
		}
		
		@Override
		public void fillPolygon(int[] points, int[] points2, int points3) {
			points = Arrays.copyOf(points, points.length);
			points2 = Arrays.copyOf(points2, points.length);
			for(int i = 0; i < points.length; ++i)
				points[i] = scalex(points[i]);
			for(int i = 0; i < points2.length; ++i)
				points2[i] = scaley(points2[i]);
			source.fillPolygon(points, points2, points3);
		}
		
		@Override
		public void fillRect(int x, int y, int width, int height) {
			source.fillRect(scalex(x), scaley(y), scalex(width), scaley(height));
		}
		
		@Override
		public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {
			source.fillRoundRect(scalex(x), scaley(y), scalex(width), scaley(height), scalex(arcWidth), scaley(arcHeight));
		}
		
		@Override
		public Shape getClip() {
			return source.getClip();
		}
		
		@Override
		public Rectangle getClipBounds() {
			return source.getClipBounds();
		}
		
		@Override
		public Color getColor() {
			return source.getColor();
		}
		
		@Override
		public Font getFont() {
			return source.getFont();
		}
		
		@Override
		public FontMetrics getFontMetrics(Font f) {
			return source.getFontMetrics(f);
		}
		
		@Override
		public void setClip(Shape clip) {
			source.setClip(clip);
		}
		
		@Override
		public void setClip(int x, int y, int width, int height) {
			source.setClip(scalex(x), scaley(y), scalex(width), scaley(height));
		}
		
		@Override
		public void setColor(Color c) {
			source.setColor(c);
		}
		
		@Override
		public void setFont(Font font) {
			source.setFont(font);
		}
		
		@Override
		public void setPaintMode() {
			source.setPaintMode();
		}
		
		@Override
		public void setXORMode(Color c1) {
			source.setXORMode(c1);
		}
	}
	
	//-------------------- VARIABLES --------------------
	
	// ××××× SETTINGS VARIABLES ×××××
	
	/**
	 * Determines whether this build of the program is for developers (true) or it is a normal build
	 * (false). Its value is manually changed before the compiling.
	 */
	final static boolean IS_DEVELOPER_BUILD = true;
	/**
	 * The error message that will be displayed, if the user trying to start the debugging tool for
	 * a specific class instead of starting the whole application with debugging mode.
	 */
	final static String NOT_DEVELOPER_STRING = "Please run the debugging tools from the main method of the KeyboardHero class with the 'debug' switch!";
	final static String DATA_FOLDER = "data/";
	final static String PACKAGE_FOLDER = KeyboardHero.class.getPackage().getName() + "/";
	
	// ××××× CACHE VARIABLES ×××××
	
	/** Indicates whether the platform the application runs on is Microsoft Windows based or not. */
	final static boolean IS_WIN = System.getProperty("os.name").toUpperCase().startsWith("WIN");
	final static String DEF_LAF = UIManager.getLookAndFeel().getClass().getName();
	/**
	 * A cached random generator used for several purposes, including the
	 * {@link Util#keygeneration()} method.
	 */
	final static Random RAND = new Random(System.currentTimeMillis());
	final static Toolkit TOOLKIT = Toolkit.getDefaultToolkit();
	final static Desktop DESKTOP = (Desktop.isDesktopSupported() ? Desktop.getDesktop() : null);
	final static Runtime RUNTIME = Runtime.getRuntime();
	
	final static ImageObserver IMAGE_OBSERVER = new ImageObserver() {
		public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
			return false; // we don't actually need info, we only would like to precache
		}
	};
	
	final static GridBagLayout GRIDBAG = new GridBagLayout();
	final static Insets GRIDINSETS = new Insets(5, 5, 5, 5);
	final static GridBagConstraints GRIDLEFT = new GridBagConstraints(GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, GRIDINSETS, 0, 0);
	final static GridBagConstraints GRIDRIGHT = new GridBagConstraints(GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE, GridBagConstraints.REMAINDER, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, GRIDINSETS, 0, 0);
	final static GridBagConstraints GRIDCENTER = new GridBagConstraints(GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE, GridBagConstraints.REMAINDER, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.NONE, GRIDINSETS, 0, 0);
	final static GridBagConstraints GRIDONLYLEFT = new GridBagConstraints(GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE, GridBagConstraints.REMAINDER, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, GRIDINSETS, 0, 0);
	final static GridBagConstraints GRIDONLYRIGHT = new GridBagConstraints(GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE, GridBagConstraints.REMAINDER, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.NONE, GRIDINSETS, 0, 0);
	
	// ××××× PROPERTIES VARIABLES ×××××
	
	/**
	 * This field stores the user specific properties of the game. These properties include the
	 * position and size of the window, language, look and feel, name of the player,
	 * {@link Util#debugLevel debug level} etc. The content of this field is read from the "."+{@value keyboardhero.KeyboardHero#APP_NAME}+".conf"
	 * file at the start of the application.
	 * 
	 * @see Util#readProps()
	 * @see Util#writeProps()
	 */
	private static Properties props = new Properties();
	/**
	 * This field stores the language specific messages display by the application. The content of
	 * this field is read from appropriate language file whenever it is required.
	 * 
	 * @see Util#readMsgs()
	 */
	private static Properties msgs = new Properties();
	
	/**
	 * The default language to be used by the application. It appears twice in the list of the
	 * languages: at the top and in its correct place in the lexicographical order.
	 * 
	 * @see Util#langInit()
	 */
	private static String defLang = "English";
	
	/**
	 * The list of the available languages. This list maybe extended depending on the languages
	 * files found in the current working environment.
	 * 
	 * @see Util#langInit()
	 */
	private static TreeSet<String> langs = new TreeSet<String>();
	
	/**
	 * Determines whether the program is in a debugging mode. If so, then the {@link Game#pause()}
	 * by a key press is changed to {@link Game#debugPause()} in the {@link Game#togglePause()}
	 * method.
	 */
	private static boolean debugMode = false;
	/**
	 * The debug level that the software runs on. This number has a direct affect to the frequency
	 * of the debug messages. 0 indicates that no debug message should be displayed.
	 */
	private static int debugLevel = 1;
	
	/**
	 * The default theme to be used by the application.
	 * 
	 * @see Util#themeInit()
	 */
	private static String defTheme = Graphs.DEFAULT_THEME;
	
	/**
	 * The list of currently available themes. This list maybe extended depending on the themes
	 * found in the current working environment.
	 * 
	 * @see Util#themeInit()
	 */
	private static TreeSet<String> themes = new TreeSet<String>();
	
	// ××××× OPTIONAL-RETURN VARIABLES ×××××
	
	private static int lastMnemonicIndex = -1, lastMnemonic = 0;
	
	// ××××× ADAPTER VARIABLES ×××××
	
	final static MouseListener LABEL_MOUSE_LISTENER = new MouseAdapter() {
		public void mouseClicked(MouseEvent e) {
			Object o = e.getSource();
			if(o instanceof JLabel) {
				JLabel l = (JLabel)e.getSource();
				Component c = l.getLabelFor();
				
				if(c instanceof AbstractButton) {
					final AbstractButton c2 = (AbstractButton)c;
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							c2.requestFocus();
							c2.setSelected(!c2.isSelected());
						}
					});
				}
				
				if(c instanceof JTextComponent) {
					final JTextComponent c2 = (JTextComponent)c;
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							c2.requestFocus();
							c2.selectAll();
						}
					});
				}
				
				if(c instanceof JComboBox) {
					final JComboBox c2 = (JComboBox)c;
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							c2.requestFocus();
							c2.showPopup();
						}
					});
				}
			}
		}
	};
	
	//-------------------- PROPERTIES FUNCTIONS --------------------
	
	// ××××××××××××××× PROPERTIES FUNCTIONS ×××××××××××××××
	
	/**
	 * Reads the properties from the file stored on the hard disk in the current working dir.
	 * 
	 * @see Util#props
	 * @see Util#writeProps()
	 */
	private static void readProps() {
		File file = new File("." + KeyboardHero.APP_NAME + ".conf");
		try {
			FileInputStream stream;
			props.load(stream = new FileInputStream(file));
			stream.close();
			defaultPropsCheck();
		} // end try
		catch(IOException e) {
			defaultPropsCheck();
			if(file.exists()) {
				error("Error while reading the properties file: ." + KeyboardHero.APP_NAME + ".conf", e.toString()); // only has affect if the debuglevel has been set previously (if this method/initialize has been manually called)
			} // end if
		} // end catch
		
		try {
			debugLevel = Integer.parseInt(getProp("debugLevel"));
		} // end try
		catch(NumberFormatException e) {
			error("Error while parsing the debugLevel: " + getProp("debugLevel"), e.toString(), 0, 0);
		} // end catch
	} // end method
	
	/**
	 * Saves the current properties to the hard disk.
	 * 
	 * @see Util#props
	 * @see Util#readProps()
	 */
	private static void writeProps() {
		try {
			FileOutputStream stream;
			/*
			// It isn't so important...
			File file = new File("."+KeyboardHero.APP_NAME+".conf");
			final boolean exists = file.exists();
			final boolean hidden = file.isHidden();
			if(IS_WIN && exists) {
				Process p = Runtime.getRuntime().exec("attrib -h \"."+KeyboardHero.APP_NAME+".conf\"");
				try {
					p.waitFor();
				} // end try
				catch(InterruptedException e) {} // end catch
			} // end if
			*/
			props.store(stream = new FileOutputStream("." + KeyboardHero.APP_NAME + ".conf"), "/* " + KeyboardHero.APP_NAME + " properties */");
			stream.close();
			/*
			if(IS_WIN && (hidden || !exists)) {
				Runtime.getRuntime().exec("attrib +h \"."+KeyboardHero.APP_NAME+".conf\"");
			} // end if
			*/
		} // end try
		catch(FileNotFoundException e) {
			if(Util.getDebugLevel() > 20) e.printStackTrace();
			error("The properties file cannot be created: ." + KeyboardHero.APP_NAME + ".conf", e.toString());
		} // end catch
		catch(IOException e) {
			error("Error while writing the properties file: ." + KeyboardHero.APP_NAME + ".conf", e.toString());
		} // end catch
	} // end method
	
	private final static void propCheck(String key, boolean value) {
		propCheck(key, (value ? "y" : "n"));
	}
	
	private final static void propCheck(String key, String value) {
		if(!props.containsKey(key)) props.put(key, value);
	}
	
	private static void propCheck(String key, int value) {
		if(!props.containsKey(key)) props.put(key, Integer.toString(value));
		else try {
			Integer.parseInt(props.getProperty(key, ""));
		} // end try
		catch(NumberFormatException e) {
			props.put(key, Integer.toString(value));
		} // end catch
	}
	
	private static void propCheck(String key, float value) {
		if(!props.containsKey(key)) props.put(key, Float.toString(value));
		else try {
			Float.parseFloat(props.getProperty(key, ""));
		} // end try
		catch(NumberFormatException e) {
			props.put(key, Float.toString(value));
		} // end catch
	}
	
	/**
	 * Checks all the properties whether they exist and they are correct. If a failure occurs, then
	 * their default value will be restored.
	 */
	private static void defaultPropsCheck() {
		// ----- Debug -----
		propCheck("debugLevel", 0);
		propCheck("debugAlert", false);
		propCheck("suppressErrors", false);
		propCheck("timedDebug", true);
		
		// ----- Settings -----
		propCheck("firstTimeRun", true);
		
		propCheck("askName", true);
		propCheck("autoPause", true);
		propCheck("performance", 50);
		
		propCheck("Device_null|null", "y|y|y");
		propCheck("gameDevPause", 85);
		
		propCheck("viewGenNoteLetters", 1);
		propCheck("viewGenDisplayKeyboard", true);
		propCheck("viewGenKeyboardLetters", 1);
		propCheck("viewGenScoreImages", true);
		propCheck("viewGenFirstKey", 48);
		propCheck("viewGenLastKey", 72);
		
		propCheck("fullscreen", false);
		propCheck("renderer", 0);
		
		propCheck("connUpdate", true);
		propCheck("connToplist", true);
		propCheck("connClients", true);
		propCheck("connPort", 32321);
		
		propCheck("proxyType", 0);
		propCheck("proxyPort", 8080);
		propCheck("proxyNoLocal", true);
		
		propCheck("speed", 1f);
		
		// ----- Positions -----
		propCheck("lastCooX", 100);
		propCheck("lastCooY", 100);
		propCheck("lastWidth", 640);
		propCheck("lastHeight", 480);
		propCheck("lastState", 0);
		
		propCheck("settingsTabIndex", 0);
		propCheck("settingsCooX", 200);
		propCheck("settingsCooY", 200);
		propCheck("settingsWidth", 440);
		propCheck("settingsHeight", 280);
		
		propCheck("rulesCooX", 120);
		propCheck("rulesCooY", 120);
		propCheck("rulesWidth", 600);
		propCheck("rulesHeight", 440);
		
		propCheck("aboutCooX", 8);
		propCheck("aboutCooY", 40);
		propCheck("aboutWidth", 625);
		propCheck("aboutHeight", 400);
		
		propCheck("toplistCooX", 200);
		propCheck("toplistCooY", 200);
		
		propCheck("newGameSortIndex", 2);
		propCheck("newGameColumnWidths", "200|300|100");
		propCheck("newGameCooX", 200);
		propCheck("newGameCooY", 200);
		propCheck("newGameWidth", 440);
		propCheck("newGameHeight", 280);
		
		propCheck("songListSortIndex", 2);
		propCheck("songListColumnWidths", "200|300|100");
		propCheck("songListCooX", 115);
		propCheck("songListCooY", 115);
		propCheck("songListWidth", 610);
		propCheck("songListHeight", 450);
		
		propCheck("deviceListSortIndex", 0);
		propCheck("deviceListColumnWidths", "300|50|50|50");
	} // end method
	
	/**
	 * Returns the value of the property specified by the given key. If no such key found, then it
	 * returns an empty string.
	 * 
	 * @param key
	 *            the key of the property to be returned.
	 * @return the value of the requested property.
	 * @see #setProp(String, String)
	 */
	final static String getProp(String key) {
		return props.getProperty(key, "");
	} // end method
	
	/**
	 * Returns the value of the property as an integer specified by the given key. If no such key
	 * found, or the value is not a number, then it returns -1.
	 * 
	 * @param key
	 *            the key of the property to be returned.
	 * @return the value as integer of the requested property.
	 * @see #setProp(String, int)
	 */
	final static int getPropInt(String key) {
		try {
			return Integer.parseInt(props.getProperty(key, "-1"));
		} // end try
		catch(NumberFormatException e) {
			error("Error while parsing \"" + key + "\" property as integer", e.toString());
			return -1;
		} // end catch
	} // end method
	
	/**
	 * Returns the value of the property as an integer specified by the given key. If no such key
	 * found, or the value is not a number, then it throws an exception.
	 * 
	 * @param key
	 *            the key of the property to be returned.
	 * @return the value as integer of the requested property.
	 * @throws NumberFormatException
	 *             throws if the stored value is not a number, or if no such key found
	 * @see #setProp(String, int)
	 */
	final static int getPropIntSafe(String key) throws NumberFormatException {
		return Integer.parseInt(props.getProperty(key, ""));
	} // end method
	
	/**
	 * Returns the value of the property as an integer specified by the given key. If no such key
	 * found, or the value is not a number, then it returns the given default value.
	 * 
	 * @param key
	 *            the key of the property to be returned.
	 * @param defValue
	 *            the value to be returned in case of failure.
	 * @return the value as integer of the requested property.
	 * @see #setProp(String, int)
	 */
	final static int getPropInt(String key, int defValue) { //$SUP-MNU$
		try {
			return Integer.parseInt(props.getProperty(key, Integer.toString(defValue)));
		} // end try
		catch(NumberFormatException e) {
			error("Error while parsing \"" + key + "\" property as integer", e.toString());
			return defValue;
		} // end catch
	} // end method
	
	/**
	 * Returns the value of the property as a floating point number specified by the given key. If
	 * no such key found, or the value is not a number, then it returns the given default value.
	 * 
	 * @param key
	 *            the key of the property to be returned.
	 * @param defValue
	 *            the value to be returned in case of failure.
	 * @return the value as integer of the requested property.
	 * @see #setProp(String, float)
	 */
	final static float getPropFloat(String key, float defValue) { //$SUP-MNU$
		try {
			return Float.parseFloat(props.getProperty(key, Float.toString(defValue)));
		} // end try
		catch(NumberFormatException e) {
			error("Error while parsing \"" + key + "\" property as a float.", e.toString());
			return defValue;
		} // end catch
	} // end method
	
	/**
	 * Returns the value of the property as an integer specified by the given key. If no such key
	 * found, or the value is not a number, then it returns false.
	 * 
	 * @param key
	 *            the key of the property to be returned.
	 * @return the value as boolean of the requested property.
	 * @see #setProp(String, boolean)
	 */
	final static boolean getPropBool(String key) {
		return props.getProperty(key, "n").equals("y");
	} // end method
	
	/**
	 * Sets the value of the property specified by the given key to the given new value.
	 * 
	 * @param key
	 *            the key of the property to be changed.
	 * @param value
	 *            the new value of the property.
	 * @see #getProp(String)
	 */
	final static void setProp(String key, String value) {
		props.setProperty(key, value);
	} // end method
	
	/**
	 * Sets the value of the property specified by the given key to the given new value. The new
	 * value given as an integer will be converted to a String.
	 * 
	 * @param key
	 *            the key of the property to be changed.
	 * @param value
	 *            the new value of the property as an integer.
	 * @see #getPropInt(String)
	 * @see #getPropInt(String, int)
	 * @see #getPropIntSafe(String)
	 */
	static void setProp(String key, int value) {
		props.setProperty(key, String.valueOf(value));
	} // end method
	
	/**
	 * Sets the value of the property specified by the given key to the given new value. The new
	 * value given as a floating point number will be converted to a String.
	 * 
	 * @param key
	 *            the key of the property to be changed.
	 * @param value
	 *            the new value of the property as a floating point number.
	 * @see #getPropFloat(String, float)
	 */
	static void setProp(String key, float value) {
		props.setProperty(key, String.valueOf(value));
	} // end method
	
	/**
	 * Sets the value of the property specified by the given key to the given new value. The new
	 * value given as a boolean will be converted to a String.
	 * 
	 * @param key
	 *            the key of the property to be changed.
	 * @param value
	 *            the new value of the property as a boolean.
	 * @see #getPropBool(String)
	 */
	final static void setProp(String key, boolean value) {
		props.setProperty(key, (value ? "y" : "n"));
	} // end method
	
	// ××××××××××××××× MESSAGE FUNCTIONS ×××××××××××××××
	
	/**
	 * Reads the language specific messages from the file stored on the hard disk in the current
	 * working dir or from any other available system resource, for example from the jar file which
	 * the application is run from.
	 * 
	 * @see Util#msgs
	 */
	static void readMsgs() {
		InputStream stream;
		String lang = getLang();
		
		try {
			msgs.load(stream = getURL(KeyboardHero.APP_NAME + "." + utf2ascii(lang) + ".lng").openStream());
			stream.close();
		} // end try
		catch(Exception e) {
			try {
				msgs.load(stream = getURL(KeyboardHero.APP_NAME + "." + lang + ".lng").openStream());
				stream.close();
			} // end try
			catch(Exception e2) {
				error("Error while reading the language file: " + KeyboardHero.APP_NAME + "." + lang + ".lng", e.toString());
				if(!lang.equals(defLang)) {
					try {
						msgs.load(stream = getURL(KeyboardHero.APP_NAME + "." + utf2ascii(defLang) + ".lng").openStream());
						stream.close();
						setProp("lang", defLang);
					} // end try
					catch(Exception e3) {
						try {
							msgs.load(stream = getURL(KeyboardHero.APP_NAME + "." + defLang + ".lng").openStream());
							stream.close();
							setProp("lang", defLang);
						} // end try
						catch(Exception e4) {
							error("Error while reading the language file: " + KeyboardHero.APP_NAME + "." + defLang + ".lng", e2.toString());
						} // end catch
					} // end catch
				} // end if
			} // end catch
		} // end catch
	} // end method
	
	/**
	 * Returns the language specific message specified by the given key. If no such key found, then
	 * it returns the key.
	 * 
	 * @param key
	 *            the key of the message to be returned.
	 * @return the requested language specific message.
	 */
	final static String getMsg(String key) {
		return msgs.getProperty(key, "!" + key + "!");
	} // end method
	
	final static String getMsg(String key, String defValue) {
		return msgs.getProperty(key, defValue);
	} // end method
	
	final static String getMsgSafe(String key) {
		if(msgs.containsKey(key)) {
			return msgs.getProperty(key);
		} else {
			return null;
		}
	} // end method
	
	private static int charToKey(char c) {
		int k = c;
		if('a' <= k && k <= 'z') k -= ('a' - 'A');
		return k;
	}
	
	/**
	 * Returns the language specific message specified by the given key. If no such key found, then
	 * it returns the key.
	 * 
	 * @param key
	 *            the key of the message to be returned.
	 * @return the requested language specific message.
	 */
	static String getMsgMnemonic(String key) {
		String val = msgs.getProperty(key, "!" + key + "!");
		final int max = val.length() - 1;
		boolean fnd = false;
		int i;
		for(i = 0; i < max; i++) {
			if(val.charAt(i) == '&') {
				lastMnemonic = charToKey(val.charAt(i + 1));
				lastMnemonicIndex = i;
				fnd = true;
				break;
			}
		}
		
		if(fnd) {
			return val.substring(0, i) + val.substring(i + 1);
		} else {
			if(val.length() != 0) {
				lastMnemonic = charToKey(val.charAt(0));
				lastMnemonicIndex = 0;
			} else {
				lastMnemonic = 0;
				lastMnemonicIndex = -1;
			}
			return val;
		}
	} // end method
	
	static int getLastMnemonic() {
		return lastMnemonic;
	} // end method
	
	static int getLastMnemonicIndex() {
		return lastMnemonicIndex;
	} // end method
	
	// ××××××××××××××× LANGUAGE FUNCTIONS ×××××××××××××××
	
	/**
	 * Checks for any other available language file in the current working directory.
	 * 
	 * @see Util#langs
	 */
	private static void langInit() {
		/* // not required -> scans for available languages 
		langs.add("English");
		langs.add("Français");
		langs.add("Magyar");
		langs.add("Română");
		langs.add("Türkçe");
		langs.add("简体中文");
		*/

		final FilenameFilter filenameFilter = new FilenameFilter() {
			public boolean accept(File dir, String name) {
				return (name.startsWith(KeyboardHero.APP_NAME + ".") && name.endsWith(".lng"));
			} // end method
		}; // end class
		
		scanfForLang(new File("."), filenameFilter);
		scanfForLang(new File(DATA_FOLDER), filenameFilter);
		scanfForLang(new File(PACKAGE_FOLDER), filenameFilter);
		scanfForLang(new File(PACKAGE_FOLDER + DATA_FOLDER), filenameFilter);
		
		scanfForLang(getURL(".", true), filenameFilter);
		scanfForLang(getURL(DATA_FOLDER, true), filenameFilter);
		scanfForLang(getURL(PACKAGE_FOLDER, true), filenameFilter);
		scanfForLang(getURL(PACKAGE_FOLDER + DATA_FOLDER, true), filenameFilter);
		
		scanfForLang(getJarDirList("", filenameFilter));
		scanfForLang(getJarDirList(DATA_FOLDER, filenameFilter));
		scanfForLang(getJarDirList(PACKAGE_FOLDER, filenameFilter));
		scanfForLang(getJarDirList(PACKAGE_FOLDER + DATA_FOLDER, filenameFilter));
		
		final String locLang = Locale.getDefault().getLanguage();
		String str;
		if(locLang.equals((new Locale("fr")).getLanguage()) && langs.contains(str = "Français")) {
			defLang = str;
		} else if(locLang.equals((new Locale("hu")).getLanguage()) && langs.contains(str = "Magyar")) {
			defLang = str;
		} else if(locLang.equals((new Locale("ro")).getLanguage()) && langs.contains(str = "Română")) {
			defLang = str;
		} else if(locLang.equals((new Locale("tr")).getLanguage()) && langs.contains(str = "Türkçe")) {
			defLang = str;
		} else if(locLang.equals((new Locale("zh_CN")).getLanguage()) && langs.contains(str = "简体中文")) {
			defLang = str;
		} else {
			defLang = "English";
			if(!langs.contains(defLang)) langs.add(defLang);
		}
		
	} // end method
	
	private static void scanfForLang(URL url, FilenameFilter filenameFilter) {
		try {
			scanfForLang(new File(url.toURI()), filenameFilter);
		} catch(Exception e) {}
	}
	
	private static void scanfForLang(File dir, FilenameFilter filenameFilter) {
		scanfForLang(dir.list(filenameFilter));
	}
	
	private static void scanfForLang(String[] files) {
		if(files != null) {
			for(int i = 0; i < files.length; i++) {
				langs.add(ascii2utf(files[i].substring(KeyboardHero.APP_NAME.length() + 1, files[i].length() - 4)));
			} // end for
		} // end else
	}
	
	/**
	 * Returns the list of the available languages.
	 * 
	 * @return the value of the {@link Util#langs} field.
	 */
	static TreeSet<String> getLangs() {
		return langs;
	} // end method
	
	static String getLang() {
		final String lang = getProp("lang");
		if(lang.equals("")) return defLang;
		return lang;
	}
	
	static String getDefLang() {
		return defLang;
	} // end method
	
	static void setLang(String lang) {
		Util.setProp("lang", lang);
		setLocale(lang);
		
		Util.readMsgs();
		KeyboardHero.updateTexts();
	}
	
	static void setLocale(String lang) {
		if(lang == null) return;
		if(lang.equals("")) lang = defLang;
		if(lang.equals("Français")) {
			final Locale locale = new Locale("fr");
			if(Locale.getDefault().getLanguage() != locale.getLanguage()) Locale.setDefault(locale);
		} else if(lang.equals("Magyar")) {
			final Locale locale = new Locale("hu");
			if(Locale.getDefault().getLanguage() != locale.getLanguage()) Locale.setDefault(locale);
		} else if(lang.equals("Română")) {
			final Locale locale = new Locale("ro");
			if(Locale.getDefault().getLanguage() != locale.getLanguage()) Locale.setDefault(locale);
		} else if(lang.equals("Türkçe")) {
			final Locale locale = new Locale("tr");
			if(Locale.getDefault().getLanguage() != locale.getLanguage()) Locale.setDefault(locale);
		} else if(lang.equals("简体中文")) {
			final Locale locale = new Locale("zn_CH");
			if(Locale.getDefault().getLanguage() != locale.getLanguage()) Locale.setDefault(locale);
		} else if(lang.equals("English")) {
			final Locale locale = new Locale("en");
			if(Locale.getDefault().getLanguage() != locale.getLanguage()) Locale.setDefault(locale);
		}
	}
	
	// ××××××××××××××× DEBUGGING FUNCTIONS ×××××××××××××××
	
	/**
	 * Returns whether the application is in debugging mode.
	 * 
	 * @return the value of the {@link Util#debugMode} field.
	 */
	final static boolean isDebugMode() {
		return debugMode;
	} // end method
	
	/**
	 * Sets the application to debugging mode.
	 * 
	 * @see Util#debugMode
	 */
	final static void setDebugMode() {
		debugMode = true;
	} // end method
	
	/**
	 * Returns the current debug level that the program runs on.
	 * 
	 * @return the value of the {@link Util#debugLevel} field.
	 */
	final static int getDebugLevel() {
		return debugLevel;
	} // end method
	
	/**
	 * Sets the current debug level that the program runs on.
	 * 
	 * @param debugLevel
	 *            the new debugLevel that should be set
	 */
	final static void setDebugLevel(final int debugLevel) {
		Util.debugLevel = debugLevel;
	} // end method
	
	// ××××××××××××××× THEME FUNCTIONS ×××××××××××××××
	
	/**
	 * Checks for any other available language file in the current working directory.
	 * 
	 * @see Util#langs
	 */
	private static void themeInit() {
		final FileFilter fileFilter = new FileFilter() {
			public boolean accept(File pathname) {
				return (pathname.isDirectory() && !pathname.getName().startsWith(".") && !pathname.isHidden());
			}
		}; // end class
		
		scanfForTheme(new File(Graphs.THEME_DIR), fileFilter);
		scanfForTheme(new File(DATA_FOLDER + Graphs.THEME_DIR), fileFilter);
		scanfForTheme(new File(PACKAGE_FOLDER + Graphs.THEME_DIR), fileFilter);
		scanfForTheme(new File(PACKAGE_FOLDER + DATA_FOLDER + Graphs.THEME_DIR), fileFilter);
		
		scanfForTheme(getURL(Graphs.THEME_DIR, true), fileFilter);
		scanfForTheme(getURL(DATA_FOLDER + Graphs.THEME_DIR, true), fileFilter);
		scanfForTheme(getURL(PACKAGE_FOLDER + Graphs.THEME_DIR, true), fileFilter);
		scanfForTheme(getURL(PACKAGE_FOLDER + DATA_FOLDER + Graphs.THEME_DIR, true), fileFilter);
		
		scanfForTheme(getJarDirList(Graphs.THEME_DIR, null));
		scanfForTheme(getJarDirList(DATA_FOLDER + Graphs.THEME_DIR, null));
		scanfForTheme(getJarDirList(PACKAGE_FOLDER + Graphs.THEME_DIR, null));
		scanfForTheme(getJarDirList(PACKAGE_FOLDER + DATA_FOLDER + Graphs.THEME_DIR, null));
		
		String cache;
		if(themes.contains(cache = Graphs.DEFAULT_THEME)) {
			defTheme = cache;
		} else if(!themes.isEmpty()) {
			defTheme = themes.first();
		} else {
			themes.add(defTheme = cache);
		}
	} // end method
	
	private static void scanfForTheme(URL url, FileFilter fileFilter) {
		try {
			scanfForTheme(new File(url.toURI()), fileFilter);
		} catch(Exception e) {}
	}
	
	private static void scanfForTheme(File dir, FileFilter fileFilter) {
		scanfForTheme(dir.listFiles(fileFilter));
	}
	
	private static void scanfForTheme(File[] files) {
		if(files != null) {
			for(int i = 0; i < files.length; i++) {
				themes.add(ascii2utf(files[i].getName()));
			} // end for
		} // end else
	}
	
	private static void scanfForTheme(String[] files) {
		if(files != null) {
			for(int i = 0; i < files.length; i++) {
				themes.add(ascii2utf(files[i]));
			} // end for
		} // end else
	}
	
	/**
	 * Returns the list of the available languages.
	 * 
	 * @return the value of the {@link Util#langs} field.
	 */
	static TreeSet<String> getThemes() {
		return themes;
	} // end method
	
	static String getTheme() {
		final String theme = getProp("theme");
		if(theme.equals("")) return defTheme;
		return theme;
	}
	
	static String getDefTheme() {
		return defTheme;
	} // end method
	
	static void setTheme(String theme) {
		Util.setProp("theme", theme);
		Graphs.setTheme(getTheme());
	}
	
	//-------------------- FILE-SYSTEM FUNCTIONS --------------------
	
	static URL getURL(final String fname) {
		URL url = null;
		File file;
		
		{
			final String pfname = fname; // <file>
			final String lfname = PACKAGE_FOLDER + fname; // keyboardhero/<file>
			final String pdatafname = DATA_FOLDER + fname; // data/<file>
			final String datafname = PACKAGE_FOLDER + DATA_FOLDER + fname; // keyboardhero/data/<file>
			
			try {
				file = new File(pfname);
				if(file.exists()) url = file.toURI().toURL();
			} catch(MalformedURLException e) {}
			if(url == null) try {
				file = new File(pdatafname);
				if(file.exists()) url = file.toURI().toURL();
			} catch(MalformedURLException e) {}
			if(url == null) try {
				file = new File(lfname);
				if(file.exists()) url = file.toURI().toURL();
			} catch(MalformedURLException e) {}
			if(url == null) try {
				file = new File(datafname);
				if(file.exists()) url = file.toURI().toURL();
			} catch(MalformedURLException e) {}
			
			if(url == null) {
				url = ClassLoader.getSystemResource(pfname);
			}
			if(url == null) {
				url = ClassLoader.getSystemResource(pdatafname);
			}
			if(url == null) {
				url = ClassLoader.getSystemResource(lfname);
			}
			if(url == null) {
				url = ClassLoader.getSystemResource(datafname);
			}
		}
		
		{
			final String pfname = "../" + fname; // <file>
			final String pdatafname = "../" + DATA_FOLDER + fname; // data/<file>
			final String lfname = fname; // keyboardhero/<file>
			final String datafname = DATA_FOLDER + fname; // keyboardhero/data/<file>
			
			if(url == null) {
				url = KeyboardHero.class.getResource(pfname);
			}
			if(url == null) {
				url = KeyboardHero.class.getResource(pdatafname);
			}
			if(url == null) {
				url = KeyboardHero.class.getResource(lfname);
			}
			if(url == null) {
				url = KeyboardHero.class.getResource(datafname);
			}
		}
		
		return url;
	}
	
	static URL getURL(final String fname, boolean inJar) {
		URL url = null;
		File file;
		
		if(!inJar) {
			final String pfname = fname; // <file>
			final String lfname = PACKAGE_FOLDER + fname; // keyboardhero/<file>
			final String pdatafname = DATA_FOLDER + fname; // data/<file>
			final String datafname = PACKAGE_FOLDER + DATA_FOLDER + fname; // keyboardhero/data/<file>
			
			try {
				file = new File(pfname);
				if(file.exists()) url = file.toURI().toURL();
			} catch(MalformedURLException e) {}
			if(url == null) try {
				file = new File(pdatafname);
				if(file.exists()) url = file.toURI().toURL();
			} catch(MalformedURLException e) {}
			if(url == null) try {
				file = new File(lfname);
				if(file.exists()) url = file.toURI().toURL();
			} catch(MalformedURLException e) {}
			if(url == null) try {
				file = new File(datafname);
				if(file.exists()) url = file.toURI().toURL();
			} catch(MalformedURLException e) {}
		} else {
			{
				final String pfname = fname; // <file>
				final String lfname = PACKAGE_FOLDER + fname; // keyboardhero/<file>
				final String pdatafname = DATA_FOLDER + fname; // data/<file>
				final String datafname = PACKAGE_FOLDER + DATA_FOLDER + fname; // keyboardhero/data/<file>
				
				url = ClassLoader.getSystemResource(pfname);
				if(url == null) {
					url = ClassLoader.getSystemResource(pdatafname);
				}
				if(url == null) {
					url = ClassLoader.getSystemResource(lfname);
				}
				if(url == null) {
					url = ClassLoader.getSystemResource(datafname);
				}
			}
			
			{
				final String pfname = "../" + fname; // <file>
				final String pdatafname = "../" + DATA_FOLDER + fname; // data/<file>
				final String lfname = fname; // keyboardhero/<file>
				final String datafname = DATA_FOLDER + fname; // keyboardhero/data/<file>
				
				if(url == null) {
					url = KeyboardHero.class.getResource(pfname);
				}
				if(url == null) {
					url = KeyboardHero.class.getResource(pdatafname);
				}
				if(url == null) {
					url = KeyboardHero.class.getResource(lfname);
				}
				if(url == null) {
					url = KeyboardHero.class.getResource(datafname);
				}
			}
			
		}
		
		return url;
	}
	
	/**
	 * Gets the content of a directory from a jar.
	 * 
	 * @param path
	 *            the path of the directory to be scanned.
	 * @param filenamefilter
	 *            the filename filter that should be applied. Value null indicates that we should
	 *            look for directories only.
	 * @return the contents of the directory or an empty list if an error occured.
	 */
	static String[] getJarDirList(String path, FilenameFilter filenamefilter) {
		URL url = KeyboardHero.class.getClassLoader().getResource(KeyboardHero.class.getName().replace(".", "/") + ".class");
		
		if(url.getProtocol().equals("jar")) {
			JarFile jar;
			try {
				final String cache = URLDecoder.decode(url.getPath(), "UTF-8");
				jar = new JarFile(cache.substring(5, cache.indexOf("!")));
			} catch(IOException e) {
				return new String[0];
			}
			
			Enumeration<JarEntry> entries = jar.entries();
			TreeSet<String> result = new TreeSet<String>();
			while(entries.hasMoreElements()) {
				String name = entries.nextElement().getName();
				if(name.startsWith(path)) {
					String entry = name.substring(path.length());
					int separator = entry.indexOf("/");
					if(separator >= 0) {
						// if it is a directory -> return the directory name only
						entry = entry.substring(0, separator);
						if(filenamefilter == null && !entry.startsWith(".")) {
							result.add(entry);
							continue;
						}
					}
					if(filenamefilter != null && filenamefilter.accept(null, entry)) {
						result.add(entry);
					}
				}
			}
			return result.toArray(new String[0]);
		}
		
		return new String[0];
	}
	
	//-------------------- GUI FUNCTIONS --------------------
	
	static void updateRecursiveUI(Component component) {
		if(component instanceof Dialog) {
			Dialog dialog = (Dialog)component;
			final boolean undecorated = UIManager.getLookAndFeel().getSupportsWindowDecorations();
			if(undecorated != dialog.isUndecorated()) {
				dialog.removeNotify();
				dialog.setUndecorated(undecorated);
				dialog.addNotify();
				dialog.createBufferStrategy(1);
			}
			if(dialog instanceof JDialog) {
				((JDialog)dialog).getRootPane().setWindowDecorationStyle(JRootPane.PLAIN_DIALOG);
			}
		}
		
		if(component instanceof JComponent) {
			JComponent jcomponent = (JComponent)component;
			jcomponent.updateUI();
			
			if(component instanceof JMenu) {
				JMenu jmenu = (JMenu)component;
				
				for(Component c : jmenu.getMenuComponents()) {
					updateRecursiveUI(c);
				}
			}
		}
		
		if(component instanceof Container) {
			Container container = (Container)component;
			for(Component c : container.getComponents()) {
				updateRecursiveUI(c);
			}
		}
	}
	
	@SuppressWarnings("unchecked")
	static <K, V> void updateRecursiveText(Component component) { // Component
		String cache;
		
		if(component instanceof Container) { // Component -> Container
			Container container = (Container)component;
			for(Component c : container.getComponents()) {
				updateRecursiveText(c);
			}
			
			if(container instanceof JComponent) { // Component -> Container -> JComponent
				JComponent jcomponent = (JComponent)container;
				final Border border = jcomponent.getBorder();
				if(border instanceof NamedTitledBorder) {
					NamedTitledBorder namedtitledborder = (NamedTitledBorder)border;
					if((cache = namedtitledborder.getName()) != null) {
						namedtitledborder.setTitle(Util.getMsgMnemonic(cache));
					}
				}
				
				if(jcomponent instanceof JLabel) { // Component -> Container -> JComponent -> JLabel
					JLabel jlabel = (JLabel)jcomponent;
					if((cache = jlabel.getName()) != null) {
						jlabel.setText(Util.getMsgMnemonic(cache));
						if(jlabel.getDisplayedMnemonic() != 0) {
							jlabel.setDisplayedMnemonic(Util.getLastMnemonic());
							jlabel.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
						}
					}
				} else if(jcomponent instanceof AbstractButton) { // Component -> Container -> JComponent -> AbstractButton
					AbstractButton abstractbutton = (AbstractButton)jcomponent;
					if((cache = abstractbutton.getName()) != null) {
						abstractbutton.setText(Util.getMsgMnemonic(cache));
						if(abstractbutton.getMnemonic() != 0) {
							abstractbutton.setMnemonic(Util.getLastMnemonic());
							abstractbutton.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
						}
					}
					
					if(abstractbutton instanceof JMenu) { // Component -> Container -> JComponent -> AbstractButton -> JMenuItem -> JMenu
						JMenu jmenu = (JMenu)abstractbutton;
						for(Component c : jmenu.getMenuComponents()) {
							updateRecursiveText(c);
						}
					}
				} else if(jcomponent instanceof JComboBox) { // Component -> Container -> JComponent -> JComboBox
					JComboBox jcombobox = (JComboBox)jcomponent;
					final int max = jcombobox.getItemCount();
					for(int i = 0; i < max; ++i) {
						final Object i1 = jcombobox.getItemAt(i);
						if(i1 instanceof Item) {
							final Item<?, ?> i2 = (Item<?, ?>)i1;
							if(i2.getValue() instanceof String) {
								final Item<?, String> i3 = (Item<?, String>)i2; // it is checked cast...
								if((cache = i3.getName()) != null) {
									i3.setValue(Util.getMsgMnemonic(cache));
								}
							}
						}
					}
				} else if(jcomponent instanceof JTabbedPane) { // Component -> Container -> JComponent -> JTabbedPane
					JTabbedPane jtabbedpane = (JTabbedPane)jcomponent;
					final int max = jtabbedpane.getTabCount();
					for(int i = 0; i < max; ++i) {
						if((cache = jtabbedpane.getComponentAt(i).getName()) != null) {
							jtabbedpane.setTitleAt(i, Util.getMsgMnemonic(cache));
							if(jtabbedpane.getMnemonicAt(i) != 0) {
								jtabbedpane.setMnemonicAt(i, Util.getLastMnemonic());
								jtabbedpane.setDisplayedMnemonicIndexAt(i, Util.getLastMnemonicIndex());
							}
							jtabbedpane.setToolTipTextAt(i, Util.getMsg(cache + "_Help"));
						}
					}
				} else if(jcomponent instanceof SongSelector) { // Component -> Container -> JComponent -> JScrollPane -> SongSelector
					SongSelector songselector = (SongSelector)jcomponent;
					songselector.updateTexts();
				}
			}
		} else { // Component
			if(component instanceof Label) { // Component -> Label
				Label c2 = (Label)component;
				if((cache = c2.getName()) != null) {
					c2.setText(Util.getMsgMnemonic(cache));
				}
			} else if(component instanceof Button) { // Component -> Button
				Button button = (Button)component;
				if((cache = button.getName()) != null) {
					button.setLabel(Util.getMsgMnemonic(cache));
				}
			}
		}
	}
	
	static void selectMenuItems(JMenu menu, String actionCommand) {
		if(menu == null) return;
		for(Component c : menu.getMenuComponents()) {
			if(c instanceof JMenuItem) {
				JMenuItem c2 = (JMenuItem)c;
				c2.setEnabled(!c2.getActionCommand().equals(actionCommand));
			}
		}
	}
	
	static void selectKey(JComboBox combobox, Object key) {
		if(key != null && key.equals("")) key = null;
		Item<?, ?> item;
		final int max = combobox.getItemCount();
		for(int i = 0; i < max; ++i) {
			item = (Item<?, ?>)combobox.getItemAt(i);
			if(equals(item.getKey(), key)) {
				combobox.setSelectedIndex(i);
				break;
			}
		}
	}
	
	final static void updateButtonText(final AbstractButton button, final String key) {
		button.setText(Util.getMsgMnemonic(key));
		button.setMnemonic(Util.getLastMnemonic());
		button.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		button.setName(key);
	}
	
	final static JLabel addLabeledComponent(Container container, String labelName, Component component) {
		return addLabeledComponent(container, labelName, component, null, null);
	}
	
	final static JLabel addLabeledComponent(Container container, String labelName, Component component, EventListener eventListener) {
		return addLabeledComponent(container, labelName, component, eventListener, null);
	}
	
	static JLabel addLabeledComponent(Container container, String labelName, Component component, EventListener eventListener, Component optionalComponent) {
		final JLabel label;
		container.add(label = new JLabel(Util.getMsgMnemonic(labelName)));
		GRIDBAG.setConstraints(label, GRIDLEFT);
		label.setName(labelName);
		label.setLabelFor(component);
		label.setDisplayedMnemonic(Util.getLastMnemonic());
		label.setDisplayedMnemonicIndex(Util.getLastMnemonicIndex());
		label.addMouseListener(Util.LABEL_MOUSE_LISTENER);
		
		if(optionalComponent != null) {
			container.add(component);
			GRIDBAG.setConstraints(component, GRIDLEFT);
			container.add(optionalComponent);
			GRIDBAG.setConstraints(optionalComponent, GRIDRIGHT);
		} else {
			container.add(component);
			GRIDBAG.setConstraints(component, GRIDRIGHT);
		}
		
		if(eventListener != null) {
			if(component instanceof AbstractButton && eventListener instanceof ChangeListener) {
				((AbstractButton)component).addChangeListener((ChangeListener)eventListener);
			} else if(component instanceof JTextComponent && eventListener instanceof DocumentListener) {
				((JTextComponent)component).getDocument().addDocumentListener((DocumentListener)eventListener);
			} else if(component instanceof JComboBox && eventListener instanceof ItemListener) {
				((JComboBox)component).addItemListener((ItemListener)eventListener);
			}
		}
		
		return label;
	}
	
	final static void addToCenter(Container container, Component component) {
		container.add(component);
		GRIDBAG.setConstraints(component, GRIDCENTER);
	}
	
	final static void addToLeft(Container container, Component component) {
		container.add(component);
		GRIDBAG.setConstraints(component, GRIDONLYLEFT);
	}
	
	final static void addToRight(Container container, Component component) {
		container.add(component);
		GRIDBAG.setConstraints(component, GRIDONLYRIGHT);
	}
	
	//-------------------- GRAPHICS FUNCTIONS --------------------
	
	static void resizeImage(final double ratio, final Image original, final Obj<Image> resized, final Integer scaleHint) {
		final int id = resized.incrementId();
		final int width = original.getWidth(new ImageObserver() {
			public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
				if(resized.getId() != id) return false;
				if((infoflags & (ImageObserver.ABORT | ImageObserver.ERROR)) != 0) return false;
				if((infoflags | ImageObserver.WIDTH) != 0) {
					resizeImage(original, (int)(width * ratio), resized, scaleHint);
					return false;
				}
				return true;
			}
		});
		
		if(width > 0) {
			resizeImage(original, (int)(width * ratio), resized, scaleHint);
		}
	}
	
	static void resizeImage(final Image original, final int width, final Obj<Image> resized, final Integer scaleHint) {
		final Image image = original.getScaledInstance(width, -1, scaleHint);
		
		final int id = resized.incrementId();
		final ImageObserver imageObserver = new ImageObserver() {
			public boolean imageUpdate(Image img, int infoflags, int x, int y, int w, int h) {
				if(resized.getId() != id) {
					return false;
				}
				if((infoflags & (ImageObserver.ABORT | ImageObserver.ERROR)) != 0) {
					return false;
				}
				if((infoflags & ImageObserver.ALLBITS) != 0) {
					resized.setObj(image);
					return false;
				}
				
				return true;
			}
		};
		
		preloadIfToolkit(image, imageObserver);
		if(image.getHeight(imageObserver) != -1) {
			imageObserver.imageUpdate(image, ImageObserver.ALLBITS, 0, 0, 0, 0);
		}
		
		(new Thread() {
			public void run() {
				try {
					Thread.sleep(1000);
				} catch(InterruptedException e) {}
				
				if(resized.getId() == id) {
					if(image.getHeight(IMAGE_OBSERVER) != resized.getObj().getWidth(IMAGE_OBSERVER)) {
						preloadIfToolkit(image, imageObserver);
						if(image.getHeight(imageObserver) != -1) {
							imageObserver.imageUpdate(image, ImageObserver.ALLBITS, 0, 0, 0, 0);
						}
					}
				}
				
				try {
					Thread.sleep(1000);
				} catch(InterruptedException e) {}
				
				if(resized.getId() == id) {
					final int cache;
					if((cache = image.getHeight(IMAGE_OBSERVER)) != -1 && cache != resized.getObj().getWidth(IMAGE_OBSERVER)) {
						resized.setObj(image);
					}
				}
			}
		}).start();
	}
	
	final static boolean preloadIfToolkit(Image image, ImageObserver imageObserver) {
		if(image instanceof sun.awt.image.ToolkitImage) {
			sun.awt.image.ToolkitImage toolkitImage = ((sun.awt.image.ToolkitImage)image);
			toolkitImage.preload(imageObserver);
			if((toolkitImage.check(imageObserver) & ImageObserver.ALLBITS) != 0) {
				imageObserver.imageUpdate(image, ImageObserver.ALLBITS, 0, 0, 0, 0);
			}
			return true;
		}
		return false;
	}
	
	final static Color getFadingColor(double ratio, Color normal, Color max) {
		final int red = normal.getRed();
		final int green = normal.getGreen();
		final int blue = normal.getBlue();
		
		return new Color(red + (int)((max.getRed() - red) * ratio), green + (int)((max.getGreen() - green) * ratio), blue + (int)((max.getBlue() - blue) * ratio));
	}
	
	//-------------------- ALERTING FUNCTIONS --------------------
	
	/**
	 * Alerts an information message.
	 * 
	 * @param msg
	 *            the content of the message.
	 * @see Util#alert(String, boolean, int, String, String)
	 */
	final static void info(String msg) { //$SUP-MNU$ //$SUP-MCP$
		alert(KeyboardHero.APP_TITLE, true, JOptionPane.INFORMATION_MESSAGE, msg, msg);
	} // end method
	
	/**
	 * Alerts an information message with the given title.
	 * 
	 * @param msg
	 *            the content of the message.
	 * @param title
	 *            the title of the message.
	 * @see Util#alert(String, boolean, int, String, String)
	 */
	final static void info(String msg, String title) { //$SUP-MNU$ //$SUP-MCP$
		alert(title, true, JOptionPane.INFORMATION_MESSAGE, msg, msg);
	} // end method
	
	/**
	 * Alerts a warning message.
	 * 
	 * @param msg
	 *            the content of the message.
	 * @see Util#alert(String, boolean, int, String, String)
	 */
	final static void warning(String msg) { //$SUP-MNU$ //$SUP-MCP$
		alert(KeyboardHero.APP_TITLE, true, JOptionPane.WARNING_MESSAGE, msg, msg);
	} // end method
	
	/**
	 * Alerts a warning message with the given title.
	 * 
	 * @param msg
	 *            the content of the message.
	 * @param title
	 *            the title of the message.
	 * @see Util#alert(String, boolean, int, String, String)
	 */
	final static void warning(String msg, String title) { //$SUP-MNU$ //$SUP-MCP$
		alert(title, true, JOptionPane.WARNING_MESSAGE, msg, msg);
	} // end method
	
	/**
	 * Alerts a debugging messgage if the {@link Util#debugLevel debugging level} is at least 1.
	 * 
	 * @param msg
	 *            the debugging message that should be displayed.
	 * @see Util#alert(String, boolean, int, String, String)
	 */
	final static void debug(Object msg) {
		debug(1, msg);
	} // end method
	
	/**
	 * Alerts a debugging messgage if the {@link Util#debugLevel debugging level} is at least the
	 * given one.
	 * 
	 * @param minDebugLevel
	 *            the minimum {@link Util#debugLevel debugging level} that the displaying of the
	 *            message requires.
	 * @param msg
	 *            the debugging message that should be displayed.
	 * @see Util#alert(String, boolean, int, String, String)
	 */
	final static void debug(int minDebugLevel, Object msg) { //$SUP-MNU$
		String str = (msg == null ? "NULL" : msg.toString());
		if(debugLevel >= minDebugLevel) {
			alert((Util.getPropBool("timedDebug") ? "Debug " + DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date()) + "." + (System.currentTimeMillis() % 1000) : "Debug"), false, JOptionPane.WARNING_MESSAGE, str, str);
		} // end if
	} // end method
	
	/**
	 * Alerts the user about an error. "An Error has occurred" will be the message of the error.
	 * 
	 * @see Util#errorAlert(String)
	 * @see Util#errorAlert(String, String)
	 */
	final static void error() { //$SUP-MNU$
		error("An Error has occurred", "", 0, 0);
	} // end method
	
	/**
	 * Alerts an error message. This method is called if an error or exception occurs. No additional
	 * message is provided by this method. The error message or the additional information will be
	 * shown if the {@link Util#debugLevel debugging level} is at least 0.
	 * 
	 * @param errorMsg
	 *            the error message to be alerted.
	 * @see Util#errorAlert(String)
	 * @see Util#errorAlert(String, String)
	 */
	final static void error(String errorMsg) { //$SUP-MNU$ //$SUP-MCP$
		error(errorMsg, null, 0, 0);
	} // end method
	
	/**
	 * Alerts an error message. This method is called if an error or exception occurs. The error
	 * message or the additional information will be shown if the
	 * {@link Util#debugLevel debugging level} is at least 0.
	 * 
	 * @param errorMsg
	 *            the error message to be alerted.
	 * @param exceptionMsg
	 *            an additional information about the error. It is usually provided by the
	 *            {@code Exception}.
	 * @see Util#errorAlert(String)
	 * @see Util#errorAlert(String, String)
	 */
	final static void error(String errorMsg, String exceptionMsg) { //$SUP-MNU$
		error(errorMsg, exceptionMsg, 0, 0);
	} // end method
	
	/**
	 * Alerts an error message. This method is called if an error or exception occurs. No additional
	 * message is provided by this method. The error message or the additional information will be
	 * shown if the {@link Util#debugLevel debugging level} is at least 0.
	 * 
	 * @param errorMsg
	 *            the error message to be alerted.
	 * @param minDebugLevel
	 *            the required {@link Util#debugLevel debug level} for displaying the error message.
	 *            Its value is usually 0, thus the user will be reported about the unexpected error.
	 * @see Util#errorAlert(String)
	 * @see Util#errorAlert(String, String)
	 */
	final static void error(String errorMsg, int minDebugLevel) { //$SUP-MNU$ //$SUP-MCP$
		error(errorMsg, null, minDebugLevel, 0);
	} // end method
	
	/**
	 * Alerts an error message. This method is called if an error or exception occurs.
	 * 
	 * @param errorMsg
	 *            the error message to be alerted.
	 * @param exceptionMsg
	 *            an additional information about the error. It is usually provided by the
	 *            {@code Exception}.
	 * @param minDebugLevel
	 *            the required {@link Util#debugLevel debug level} for displaying the error message
	 *            and the additional information. Its value is usually 0, thus the user will be
	 *            reported about the unexpected error.
	 * @see Util#errorAlert(String)
	 * @see Util#errorAlert(String, String)
	 */
	final static void error(String errorMsg, String exceptionMsg, int minDebugLevel) { //$SUP-MNU$
		error(errorMsg, exceptionMsg, minDebugLevel, minDebugLevel);
	} // end method
	
	/**
	 * Alerts an error message. This method is called if an error or exception occurs.
	 * 
	 * @param errorMsg
	 *            the error message to be alerted.
	 * @param exceptionMsg
	 *            an additional information about the error. It is usually provided by the
	 *            {@code Exception}.
	 * @param minDebugLevel
	 *            the required {@link Util#debugLevel debug level} for displaying the error message.
	 *            Its value is usually 0, thus the user will be reported about the unexpected error.
	 * @param exceptionMinDebugLevel
	 *            the required {@link Util#debugLevel debug level} for displaying the additinal
	 *            information. Its value is usually 0, thus the user will be reported about the
	 *            unexpected error.
	 * @see Util#errorAlert(String)
	 * @see Util#errorAlert(String, String)
	 */
	static void error(String errorMsg, String exceptionMsg, int minDebugLevel, int exceptionMinDebugLevel) { //$SUP-MCP$ //$SUP-MAP$
		if(debugLevel >= minDebugLevel && debugLevel >= exceptionMinDebugLevel) {
			if(errorMsg != null && !errorMsg.equals("") && exceptionMsg != null && !exceptionMsg.equals("")) {
				errorAlert(withExclamationMark(Util.withAdditionalCharacter(errorMsg, (Character.isLowerCase(exceptionMsg.charAt(0)) ? ';' : '!')) + exceptionMsg), withExclamationMark(withExclamationMark(errorMsg) + "\n\n" + exceptionMsg));
			} // end if
			else {
				errorAlert(withExclamationMark((errorMsg != null ? errorMsg : "") + (exceptionMsg != null ? exceptionMsg : "")));
			} // end else
		} // end if
		else if(debugLevel >= minDebugLevel) {
			errorAlert(withExclamationMark(errorMsg));
		} // end if
		else if(debugLevel >= exceptionMinDebugLevel) {
			errorAlert(withExclamationMark(exceptionMsg));
		} // end if
		
		waitKeyPress(512, "DebugLevel: " + debugLevel + "; please press an enter to continue!");
		
		if(debugLevel >= 1024) {
			System.err.println("DebugLevel: " + debugLevel + "; now exiting!");
			System.exit((-1 * minDebugLevel) - 1);
		} // end if
	} // end method
	
	/**
	 * Alerts an error message. This method is only a helping method of the
	 * {@link Util#error(String, String, int, int) error} method.
	 * 
	 * @param errorMsg
	 *            the message that should be printed to the error stream and displayed in the dialog
	 *            as well.
	 * @see Util#alert(String, boolean, int, String, String)
	 */
	private final static void errorAlert(String errorMsg) { //$SUP-MCP$
		errorAlert(errorMsg, errorMsg);
	} // end method
	
	/**
	 * Alerts an error message. This method is only a helping method of the
	 * {@link Util#error(String, String, int, int) error} method.
	 * 
	 * @param errorMsg
	 *            the message that should be printed to the error stream
	 * @param alertMsg
	 *            the message that should be displayed in the dialog.
	 * @see Util#alert(String, boolean, int, String, String)
	 */
	private static void errorAlert(String errorMsg, String alertMsg) { //$SUP-MCP$ //$SUP-MAP$
		String msg = getMsg("Error");
		if(msg.length() == 0) msg = "Error";
		alert(msg, true, JOptionPane.ERROR_MESSAGE, errorMsg, alertMsg);
	} // end method
	
	/**
	 * This method makes an alert. This method prints the error message to the standard error stream
	 * and displays a graphical dialog as well. The error message created by this method can be
	 * suppressed by changing the {@code suppressErrors} property to yes ({@code y}).
	 * 
	 * @param title
	 *            the title of the error.
	 * @param alwaysAlert
	 *            if this value is true, then the error dialog will be displayed even if the
	 *            {@code debugAlert} property is not set to yes ({@code y}). This parameter is
	 *            true when an error occurs and it is false if it is only a debugging message.
	 * @param type
	 *            the type of the dialog indicating its nature.
	 * @param errorMsg
	 *            the message printed to the error stream.
	 * @param alertMsg
	 *            the message displayed in the dialog.
	 * @see KeyboardHero#alert(String, String, int)
	 */
	private static void alert(final String title, boolean alwaysAlert, final int type, final String errorMsg, final String alertMsg) { //$SUP-MAP$
		if(getPropBool("suppressErrors")) return;
		if(type == JOptionPane.INFORMATION_MESSAGE) {
			if(!debugMode) System.out.println("#" + title + ": " + errorMsg);
		} // end if
		else {
			System.err.println("#" + title + ": " + errorMsg);
		} // end else
		if(alwaysAlert || getPropBool("debugAlert")) {
			(new Thread() {
				public void run() {
					KeyboardHero.alert(alertMsg, title, type);
				}
			}).start();
		} // end if
	} // end method
	
	/**
	 * {@link Game#debugPause() Debug-pauses} the game, waits for an enter, and then
	 * {@link Game#debugResume() debug-resumes} the game. There will be no additional message for
	 * the user, and the action will happen if the {@link Util#debugLevel debug level} is at least
	 * 0.
	 */
	final static void waitKeyPress() { //$SUP-MNU$
		waitKeyPress(0, "");
	} // end method
	
	/**
	 * {@link Game#debugPause() Debug-pauses} the game, waits for an enter, and then
	 * {@link Game#debugResume() debug-resumes} the game. This action will happen if the
	 * {@link Util#debugLevel debug level} is at least 0.
	 * 
	 * @param str
	 *            the message to be printed to the user.
	 */
	final static void waitKeyPress(String str) { //$SUP-MNU$
		waitKeyPress(0, str);
	} // end method
	
	/**
	 * {@link Game#debugPause() Debug-pauses} the game, waits for an enter, and then
	 * {@link Game#debugResume() debug-resumes} the game. There will be no additional message for
	 * the user.
	 * 
	 * @param minDebugLevel
	 *            the required {@link Util#debugLevel debugging level} for this action.
	 */
	final static void waitKeyPress(int minDebugLevel) { //$SUP-MNU$
		waitKeyPress(minDebugLevel, "");
	} // end method
	
	/**
	 * {@link Game#debugPause() Debug-pauses} the game, waits for an enter, and then
	 * {@link Game#debugResume() debug-resumes} the game.
	 * 
	 * @param minDebugLevel
	 *            the required {@link Util#debugLevel debugging level} for this action.
	 * @param str
	 *            the message to be printed to the user.
	 */
	static void waitKeyPress(int minDebugLevel, String str) { //$SUP-MCP$
		if(debugLevel < minDebugLevel) return;
		
		Game.debugPause();
		System.out.println("[ ! PRESS AN ENTER ! ] " + str);
		try {
			BufferedReader bufferedRdr = new BufferedReader(new InputStreamReader(System.in));
			bufferedRdr.readLine();
		} // end try
		catch(IOException e) {
			error("Error while reading from the standard input at waitKeyPress", e.toString());
		} // end catch
		Game.debugResume();
	} // end method
	
	/**
	 * Opens a dialog asking the name of the player. This method is used whenever the user reaches a
	 * place in one of the toplists. If the user clicks the cancel button, then no change will be
	 * made in the toplists. If the {@code onlineToplist} property is set to yes, then the program
	 * will ask for the user's name at the end of every game, because this name will be sent to the
	 * server.
	 * 
	 * @return the typed name, or null if the user clicked on the cancel button or closed the
	 *         dialog.
	 * @see KeyboardHero#input(String, String)
	 */
	static boolean getPlayerName() {
		if(Util.getPropBool("askName") || Util.getProp("name").equals("")) {
			String response = KeyboardHero.input(getMsg("NameAsking"), getProp("name"));
			if(response == null) {
				return true;
			} // end if
			if(!response.equals(getProp("name"))) {
				Connection.writeTotalScore();
				setProp("name", response);
				Connection.readTotalScore();
				Connection.sendName();
			}
		}
		return false;
	} // end method
	
	static void conditionalError(Status status, String errorMsg) {
		conditionalError(status, errorMsg, null);
	}
	
	static void conditionalError(Status status, String errorMsg, String exceptionMsg) {
		if(exceptionMsg != null && !exceptionMsg.equals("")) {
			status.exception = exceptionMsg;
		}
		
		KeyboardHero.addStatus(status);
		if(!KeyboardHero.isStartup()) {
			Util.error(Util.getMsg(errorMsg), exceptionMsg);
		}
	}
	
	//-------------------- STRING MANIPULATING FUNCTIONS --------------------
	
	/**
	 * Creates a string containing the given character the given times. Hence, the length of the
	 * created string will be the requested one.
	 * 
	 * @param c
	 *            the character to be repeated.
	 * @param len
	 *            the number of times the character should be repeated.
	 * @return the created repetition of the character as a string.
	 */
	static String charRepeat(char c, int len) {
		char[] cs = new char[len];
		for(int i = 0; i < len; i++) {
			cs[i] = c;
		} // end for
		return new String(cs);
	} // end method
	
	/**
	 * Reverses the given string. It is used to check the entries inside the local toplist file.
	 * 
	 * @param str
	 *            the string the be reversed.
	 * @return the reversed string.
	 */
	static String stringReverse(final String str) {
		char cs[] = new char[str.length()];
		final int len = cs.length - 1;
		for(int i = 0; i <= len; i++) {
			cs[i] = str.charAt(len - i);
		} // end for
		return new String(cs);
	} // end method
	
	static String microToStr(long micro) {
		micro /= 1000000;
		int sec = (int)(micro % 60);
		return (micro / 60) + ":" + (sec < 10 ? "0" + sec : Integer.toString(sec));
	}
	
	static String floatToSpeed(float f) {
		if(Math.floor(f) == f) {
			return (int)f + "x";
		} else {
			return f + "x";
		}
	}
	
	static String wordWrap(String str, int width) {
		StringBuffer buf = new StringBuffer(str);
		int lastspace = -1;
		int length = 0;
		int i = 0;
		
		while(i < buf.length()) {
			if(buf.charAt(i) == '\n') {
				lastspace = -1;
				length = 0;
			} else {
				if(buf.charAt(i) == ' ') lastspace = i;
				if(width < ++length) {
					if(lastspace != -1) {
						buf.setCharAt(lastspace, '\n');
						length = i - lastspace;
					} else {
						buf.insert(i, '\n');
						length = 0;
					}
					lastspace = -1;
				}
			}
			++i;
		}
		return buf.toString();
	}
	
	static String utf2ascii(String str) {
		StringBuffer buff = new StringBuffer();
		
		for(char c : str.toCharArray()) {
			if(c == '_') {
				buff.append("__");
			} else if(c > 128) {
				buff.append('_');
				buff.append((char)HEXE[c / 4096]);
				buff.append((char)HEXE[(c / 256) % 16]);
				buff.append((char)HEXE[(c % 256) / 16]);
				buff.append((char)HEXE[c % 16]);
			} else {
				buff.append(c);
			}
		}
		
		return buff.toString();
	}
	
	private static boolean isHexValue(char c) {
		return ('A' <= c && c <= 'F') || ('0' <= c && c <= '9');
	}
	
	static String ascii2utf(String str) {
		StringBuffer buff = new StringBuffer();
		char[] chars = str.toCharArray();
		int max = str.length() - 4;
		
		int i;
		for(i = 0; i < max; ++i) {
			if(chars[i] == '_') {
				if(chars[i + 1] == '_') {
					buff.append('_');
					++i;
				} else {
					if(isHexValue(chars[i + 1]) && isHexValue(chars[i + 2]) && isHexValue(chars[i + 3]) && isHexValue(chars[i + 4])) {
						buff.append((char)(HEXD[chars[i + 1]] * 4096 + HEXD[chars[i + 2]] * 256 + HEXD[chars[i + 3]] * 16 + HEXD[chars[i + 4]]));
						i += 4;
					} else {
						buff.append(chars[i]);
					}
				}
			} else {
				buff.append(chars[i]);
			}
		}
		
		max += 3;
		for(; i < max; ++i) {
			if(chars[i] == '_') {
				buff.append('_');
				if(chars[i + 1] == '_') {
					++i;
				}
			} else {
				buff.append(chars[i]);
			}
		}
		if(i == max) buff.append(chars[i]);
		
		return buff.toString();
	}
	
	static final String withExclamationMark(String str) {
		return withAdditionalCharacter(str, '!');
	}
	
	static String withAdditionalCharacter(String str, char chr) {
		if(str == null) return null;
		switch(str.charAt(str.length() - 1)) {
			case '.':
			case '!':
			case '?':
			case ';':
				return str;
			default:
				return str + chr;
		}
	}
	
	//-------------------- PASSKEY FUNCTIONS --------------------
	
	/**
	 * Generates a key which is requested in the normal builds for the access to the debugging or
	 * testing features. This method can be called only from the developers' builds, from their
	 * debugging menu.
	 * 
	 * @see KeyboardHero#setPasskey(String)
	 */
	static void keygeneration() {
		byte[] bs = new byte[20];
		int sum = 0;
		for(int i = 0; i != 16; i += 1) { // $SUP-SU$ // $SUP-CSR$ // $SUP-IL$ // $SUP-EVC$ // $SUP-DBM$
			final int r = RAND.nextInt(16);
			bs[KEYNUMS[i]] = HEXE[r];
			sum += r;
		} // end for
		int r = RAND.nextInt(16);
		int ts = r;
		bs[2] = HEXE[r]; // $SUP-SU$
		r = RAND.nextInt(16);
		ts *= r;
		bs[9] = HEXE[r];
		ts = Math.abs(sum - ts);
		bs[5] = HEXE[ts % 16];
		bs[16] = HEXE[ts / 16];
		KeyboardHero.setPasskey(new String(bs));
	} // end method
	
	/**
	 * Checks whether the given key is a valid one. A valid key is required to be entered at the
	 * first time when the user would like to have access to the debugging or testing features.
	 * 
	 * @param passkey
	 *            the key to be checked.
	 * @return true, if it is valid.
	 */
	static boolean keycheck(String passkey) {
		if(passkey.length() != 20) return false;
		byte[] bs = passkey.getBytes();
		int sum = 0;
		for(int i = 0; i < 16; i++) {
			sum += HEXD[bs[KEYNUMS[i]]];
		} // end for
		return Math.abs(HEXD[bs[2]] * HEXD[bs[9]] - sum) == (HEXD[bs[5]] * 16 + HEXD[bs[16]]);
	} // end method
	
	//-------------------- GENERAL UTILITY FUNCTIONS --------------------
	
	final static boolean equals(Object o1, Object o2) {
		if(o1 == null) return (o2 == null);
		else return o1.equals(o2);
	}
	
	static int[] toArray(Collection<Integer> list) {
		final int[] array = new int[list.size()];
		int i = 0;
		for(int item : list) {
			array[i++] = item;
		}
		return array;
	}
	
	static byte[] toArray(Collection<Byte> list) {
		final byte[] array = new byte[list.size()];
		int i = 0;
		for(byte item : list) {
			array[i++] = item;
		}
		return array;
	}
	
	static <T> boolean contains(T[] array, T element) {
		for(int i = 0; i < array.length; ++i) {
			if(equals(element, array[i])) return true;
		}
		return false;
	}
	
	static int[] UUID2ints(UUID id) {
		int[] ints = new int[4];
		long m = id.getMostSignificantBits();
		long l = id.getLeastSignificantBits();
		
		ints[0] = (int)(m >> 32);
		ints[1] = (int)m;
		ints[2] = (int)(l >> 32);
		ints[3] = (int)l;
		
		return ints;
	}
	
	static UUID ints2UUID(int[] ints) {
		if(ints.length < 4) return null;
		
		return new UUID((ints[0] < 0 ? ints[0] + 4294967296l : ints[0]) * 4294967296l + (ints[1] < 0 ? ints[1] + 4294967296l : ints[1]), (ints[2] < 0 ? ints[2] + 4294967296l : ints[2]) * 4294967296l + (ints[3] < 0 ? ints[3] + 4294967296l : ints[3]));
	}
	
	static boolean equals(byte[] a1, byte[] a2, int limit) {
		if(a1.length < limit) limit = a1.length;
		if(a2.length < limit) limit = a2.length;
		
		for(int i = 0; i < limit; ++i) {
			if(a1[i] != a2[i]) return false;
		}
		return true;
	}
	
	//-------------------- EVENT FUNCTIONS --------------------
	
	/**
	 * Closes the class by saving the properties to the hard disk.
	 * 
	 * @see Util#writeProps()
	 */
	final static void closure() {
		setProp("debugLevel", debugLevel);
		writeProps();
	} // end method
	
	//-------------------- DEBUGGING FUNCTIONS --------------------
	
	/**
	 * Creates a string containing the most important information about the Util class. This method
	 * is focused on the runtime or platform specific fields. It is used only for debugging and
	 * testing purposes.
	 * 
	 * @return the created string.
	 */
	static String getString() {
		return "Util(debugMode=" + debugMode + "; debugLevel=" + debugLevel + "; IS_WIN=" + IS_WIN + ")";
	} // end method
	
	/**
	 * This method serves security purposes. Provides an integrity string that will be checked by
	 * the {@link Connection#integrityCheck()} method; thus the application can only be altered if
	 * the source is known. Every class in the {@link keyboardhero} package has an integrity string.
	 * 
	 * @return the string of this class used for integrity checking.
	 */
	static String getIntegrityString() {
		return "a$sd'sEP\"s-×dfAS";
	} // end method
	
	/**
	 * The tester object of this class. It provides a debugging menu and unit tests for this class.
	 * Its only purpose is debugging or testing.
	 */
	final static Tester TESTER = new Tester("Util", new String[] {"getString()", "utf2ansi", "ansi2utf"} /* end initialization */) { // $SUP-PPA$
		void menu(int choice) throws Exception {
			switch(choice) {
				case 5:
					System.out.println(getString());
					break;
				case 6:
					System.out.println(utf2ascii(readString("String str")));
					break;
				case 7:
					System.out.println(ascii2utf(readString("String str")));
					break;
				default:
					baseMenu(choice);
					break;
			} // end switch
		} // end method
		
		void runUnitTests() throws Exception {
			higherTestStart("Util");
			
			testEq("getIntegrityString()", "8ł2+*43-+-5zSDFasd", getIntegrityString());
			
			testEq("utf2ansi,ansi2utf", "aáéűÚ_ä", ascii2utf(utf2ascii("aáéűÚ_ä")));
			
			higherTestEnd();
		} // end method
		
		boolean isAutoSandbox() {
			return true;
		}
		
		void sandbox() throws Throwable {
			
			// Testing code can be put here
			
		}
	}; // end class
	
	/**
	 * Starts the class's developing menu. If this build is a developer's one it starts the
	 * application in a normal way with the exception that it starts the debugging tool for this
	 * class as well; otherwise exits with an error message.
	 * 
	 * @param args
	 *            the arguments given to the program.
	 * @see KeyboardHero#startApp()
	 */
	public static void main(String[] args) {
		Tester.mainer(args, TESTER);
	} // end method
	
	//-------------------- ENCRYPTING VARIABLES --------------------
	
	/** A secret array used in the {@link Util#keygeneration()} method. */
	private final static int[] KEYNUMS = {0, 1, 3, 4, 6, 7, 8, 10, 11, 12, 13, 14, 15, 17, 18, 19}; // end initialization; length = 16
	
	/**
	 * An array that helps converting the decimal numbers from 0 to 15 to their hexadecimal values
	 * from 0 to F.
	 * 
	 * @see Connection#encrypt(String)
	 * @see Util#keygeneration()
	 */
	final static byte[] HEXE = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; // end initialization
	/**
	 * An array that helps converting the hexadecimal numerals from 0 to F to their decimal values
	 * from 0 to 15.
	 * 
	 * @see Connection#decrypt(String)
	 * @see Connection#hexdecode(String)
	 * @see Util#keycheck(String)
	 */
	final static byte[] HEXD = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15}; // end initialization
	
	//-------------------- INITIALIZER --------------------
	
	/**
	 * This is a static initializer, so the whole class can initialize automatically.
	 */
	static {
		langInit();
		readProps();
		setLocale(Util.getProp("lang"));
		if(Util.getDebugLevel() < 100) {
			final String lang = Util.getProp("lang");
			if(!lang.equals("English") && !Util.getLang().equals("English")) {
				Util.setProp("lang", "English");
				readMsgs();
				Util.setProp("lang", lang);
			}
		}
		readMsgs();
		KeyboardHero.setLoadingString(Util.getMsg("Loading"));
		themeInit();
		Graphs.setTheme(Util.getTheme());
	}
} // end class
